﻿using System;
using System.IO;
using Exceptions;
using RSA;

namespace RSA
{
    /// <summary>
    /// RSA public key data
    /// </summary>
    /// <note>http://msdn.microsoft.com/en-us/library/aa387685(v=VS.85).aspx</note>
    internal struct RSAPubKey
    {
        /// <summary>
        /// The magic member must be set to 0x31415352 (public only) / 0x32415352 (including private). This hex value is the ASCII encoding of RSA1 / RSA2.
        /// </summary>
        public uint Magic;

        /// <summary>
        /// # of bits in modulus
        /// </summary>
        public uint BitLength;

        /// <summary>
        /// Public exponent
        /// </summary>
        public uint PublicExponent;

        #region FromBinary - Create and initialize structure from binary data

        /// <summary>
        /// Create and initialize structure from binary data
        /// </summary>
        /// <exception cref="CryptographicException">On validate errors</exception>
        /// <returns>Initialized structure</returns>
        public static RSAPubKey FromBinary(BinaryReader reader)
        {
            var rsaPubKey = new RSAPubKey
            {
                Magic = reader.ReadUInt32(),
                BitLength = reader.ReadUInt32(),
                PublicExponent = reader.ReadUInt32()
            };

            // Validate
            if (rsaPubKey.Magic != 0x31415352 && rsaPubKey.Magic != 0x32415352)
                throw new CryptographicException(string.Format("Invalid magic number [0x{0:X4}] in RSAPubKey",
                                                               rsaPubKey.Magic));

            if (rsaPubKey.BitLength % 8 != 0)
                throw new CryptographicException(string.Format("Invalid # of bits in modulus [{0}] in RSAPubKey",
                                                               rsaPubKey.BitLength));

            return rsaPubKey;
        }

        #endregion

        #region ToBinary - Serializes structure as binary data

        /// <summary>
        /// Serializes structure as binary data
        /// </summary>
        public void ToBinary(BinaryWriter writer)
        {
            writer.Write(Magic);
            writer.Write(BitLength);
            writer.Write(PublicExponent);
        }

        #endregion

        #region FromRSAParameters - Create and initialize structure from RSAParameters

        /// <summary>
        /// Create and initialize structure from RSAParameters
        /// </summary>
        /// <returns>Initialized structure</returns>
        public static RSAPubKey FromRSAParameters(RSAParameters @params, bool includePrivateParameters)
        {
            var rsaPubKey = new RSAPubKey
            {
                Magic = (uint)(includePrivateParameters ? 0x32415352 : 0x31415352),
                BitLength = (uint)(@params.N.Length << 3),
            };

            var bytes = new byte[sizeof(uint)];
            bytes[sizeof(uint) - 1] = 0;

            for (int i = 0; i < @params.E.Length; i++)
            {
                bytes[i] = @params.E[@params.E.Length - i - 1];
            }

            rsaPubKey.PublicExponent = BitConverter.ToUInt32(bytes, 0);

            return rsaPubKey;
        }

        #endregion

        #region ToRSAParameters - Create and initialize RSAParameters structure

        /// <summary>
        /// Create and initialize RSAParameters structure
        /// </summary>
        /// <returns>Initialized structure</returns>
        /// <note>http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx</note>
        public RSAParameters ToRSAParameters()
        {
            var bytes = BitConverter.GetBytes(PublicExponent);

            var @params = new RSAParameters
            {
                E = new byte[bytes[sizeof(uint) - 1] == 0 ? sizeof(uint) - 1 : sizeof(uint)]
            };

            for (int i = 0; i < @params.E.Length; i++)
            {
                @params.E[i] = bytes[@params.E.Length - i - 1];
            }

            return @params;
        }

        #endregion

        #region ToString overriding

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("BitLength: {0} | PublicExponent: {1:X4}", BitLength, PublicExponent);
        }

        #endregion
    }
}
